//! program structure compiler
use std::{iter::Peekable, slice::Iter};

use crate::{
    data::{KeywordType, SymbolType},
    error::{CompilerError, CompilerResult},
    token::Token,
};

pub(super) struct ProgramCompilerUnit;

impl ProgramCompilerUnit {
    /// The compiler flow unit of class
    pub(super) fn compile_class(tokens: &mut Peekable<Iter<'_, Token>>) -> CompilerResult<String> {
        let mut output = vec!["<class>".to_string()];
        // class
        output.push(super::Analyzer::match_keyword(
            tokens
                .next()
                .ok_or_else(|| CompilerError::Program("must start with class ;".into()))?,
            KeywordType::Class,
        )?);
        // calssName
        output.push(
            super::expression::ExpressionCompilerUnit::flow_unit_identifier(
                tokens
                    .next()
                    .ok_or_else(|| CompilerError::Program("miss className".into()))?,
            )?,
        );
        // {
        output.push(super::Analyzer::match_symbol(
            tokens
                .next()
                .ok_or_else(|| CompilerError::Program("miss {".into()))?,
            SymbolType::OpenBrace,
        )?);
        // classVarDec*
        while let Some(Token::Keyword(keyword)) = tokens.peek()
            && [KeywordType::Static, KeywordType::Field].contains(keyword)
        {
            output.push(Self::compile_class_var_dec(tokens)?);
        }
        // subroutineDec*
        while let Some(Token::Keyword(keyword)) = tokens.peek()
            && [
                KeywordType::Constructor,
                KeywordType::Function,
                KeywordType::Method,
            ]
            .contains(keyword)
        {
            output.push(Self::compile_subroutine_dec(tokens)?);
        }
        // }
        output.push(super::Analyzer::match_symbol(
            tokens
                .next()
                .ok_or_else(|| CompilerError::Program("miss }".into()))?,
            SymbolType::CloseBrace,
        )?);
        output.push("</class>".into());
        Ok(output.join("\n"))
    }
    /// The compiler flow unit of class var decscript
    fn compile_class_var_dec(tokens: &mut Peekable<Iter<'_, Token>>) -> CompilerResult<String> {
        let mut output = vec!["<classVarDec>".to_string()];
        // static | field
        if let Some(Token::Keyword(keyword)) = tokens.next()
            && [KeywordType::Static, KeywordType::Field].contains(keyword)
        {
            output.push(keyword.to_xml());
            output.push(Self::compile_type_with_one_and_more_var_name(tokens)?);
            output.push(super::Analyzer::match_symbol(
                tokens
                    .next()
                    .ok_or_else(|| CompilerError::Program("must end with ;".into()))?,
                SymbolType::Semicolon,
            )?);

            output.push("</classVarDec>".into());
            Ok(output.join("\n"))
        } else {
            Err(CompilerError::Program(
                "class var dec must start with static or field".into(),
            ))
        }
    }
    /// The compiler flow unit of subroutine dec
    fn compile_subroutine_dec(tokens: &mut Peekable<Iter<'_, Token>>) -> CompilerResult<String> {
        let mut output = vec!["<subroutineDec>".to_string()];
        // constructor | function | method
        if let Some(Token::Keyword(keyword)) = tokens.next()
            && [
                KeywordType::Constructor,
                KeywordType::Function,
                KeywordType::Method,
            ]
            .contains(keyword)
        {
            output.push(keyword.to_xml());
            // void | type
            match tokens.peek() {
                Some(Token::Keyword(keyword)) if *keyword == KeywordType::Void => {
                    // skip void
                    let _ = tokens.next();
                    output.push(keyword.to_xml())
                }
                _ => output.push(Self::compile_type(tokens, false)?),
            }
            // subroutine name
            output.push(
                super::expression::ExpressionCompilerUnit::flow_unit_identifier(
                    tokens
                        .next()
                        .ok_or_else(|| CompilerError::Program("miss subroutine name".into()))?,
                )?,
            );
            // (
            output.push(super::Analyzer::match_symbol(
                tokens
                    .next()
                    .ok_or_else(|| CompilerError::Program("miss parameterlist start (".into()))?,
                SymbolType::OpenParenthesis,
            )?);
            // parameterlist
            output.push(Self::compile_parameter_list(tokens)?);
            // )
            output.push(super::Analyzer::match_symbol(
                tokens
                    .next()
                    .ok_or_else(|| CompilerError::Program("miss parameterlist end )".into()))?,
                SymbolType::CloseParenthesis,
            )?);
            output.push(Self::compile_subroutine_body(tokens)?);
        } else {
            return Err(CompilerError::Program(
                "subroutine must start with constructor function method or void".into(),
            ));
        }
        output.push("</subroutineDec>".into());
        Ok(output.join("\n"))
    }
    /// The compiler flow unit of parameter list
    fn compile_parameter_list(tokens: &mut Peekable<Iter<'_, Token>>) -> CompilerResult<String> {
        let mut output = vec!["<parameterList>".to_string()];
        // type
        if Self::compile_type(tokens, true).is_ok() {
            // has parameter desc
            // type varName
            output.push(Self::compile_type_var_name(tokens)?);
            // (,type varName)*
            while let Some(Token::Symbol(SymbolType::Comma)) = tokens.peek() {
                output.push(SymbolType::Comma.to_xml());
                let _ = tokens.next();
                // type varName
                output.push(Self::compile_type_var_name(tokens)?);
            }
        }

        output.push("</parameterList>".into());
        Ok(output.join("\n"))
        //Ok(output.join(if output.len() == 2 { "" } else { "\n" }))
    }

    /// The compiler flow unit of subroutine body
    fn compile_subroutine_body(tokens: &mut Peekable<Iter<'_, Token>>) -> CompilerResult<String> {
        let mut output = vec!["<subroutineBody>".to_string()];
        // {
        output.push(super::Analyzer::match_symbol(
            tokens.next().ok_or_else(|| {
                CompilerError::Program("subroutine body must start with {".into())
            })?,
            SymbolType::OpenBrace,
        )?);
        // varDec*
        while let Some(Token::Keyword(KeywordType::Var)) = tokens.peek() {
            output.push(Self::compile_var_dec(tokens)?);
        }
        // statements
        output.push(super::statement::StatementCompilerUnit::compile_statements(
            tokens,
        )?);
        // }
        output.push(super::Analyzer::match_symbol(
            tokens
                .next()
                .ok_or_else(|| CompilerError::Program("subroutine body must end with }".into()))?,
            SymbolType::CloseBrace,
        )?);

        output.push("</subroutineBody>".into());

        Ok(output.join("\n"))
    }

    /// The compiler flow unit of varDec
    fn compile_var_dec(tokens: &mut Peekable<Iter<'_, Token>>) -> CompilerResult<String> {
        let mut output = vec!["<varDec>".to_string()];
        // var
        output.push(super::Analyzer::match_keyword(
            tokens
                .next()
                .ok_or_else(|| CompilerError::Program("must start with var".into()))?,
            KeywordType::Var,
        )?);
        output.push(Self::compile_type_with_one_and_more_var_name(tokens)?);
        output.push(super::Analyzer::match_symbol(
            tokens
                .next()
                .ok_or_else(|| CompilerError::Program("must end with ;".into()))?,
            SymbolType::Semicolon,
        )?);
        output.push("</varDec>".into());
        Ok(output.join("\n"))
    }

    /// The compiler flow unit of type varName (, varName)*
    fn compile_type_with_one_and_more_var_name(
        tokens: &mut Peekable<Iter<'_, Token>>,
    ) -> CompilerResult<String> {
        // type varName
        let mut output = vec![Self::compile_type_var_name(tokens)?];

        // , varName
        while let Some(Token::Symbol(SymbolType::Comma)) = tokens.peek() {
            let _ = tokens.next();
            output.push(SymbolType::Comma.to_xml());
            output.push(
                super::expression::ExpressionCompilerUnit::flow_unit_identifier(
                    tokens
                        .next()
                        .ok_or_else(|| CompilerError::Program("miss varName".into()))?,
                )?,
            );
        }
        Ok(output.join("\n"))
    }

    /// The compiler flow unit of type varName
    fn compile_type_var_name(tokens: &mut Peekable<Iter<'_, Token>>) -> CompilerResult<String> {
        // type
        let mut output = vec![Self::compile_type(tokens, false)?];
        // varName
        output.push(
            super::expression::ExpressionCompilerUnit::flow_unit_identifier(
                tokens
                    .next()
                    .ok_or_else(|| CompilerError::Program("miss varName".into()))?,
            )?,
        );

        Ok(output.join("\n"))
    }

    /// The compiler flow unit of type
    fn compile_type(
        tokens: &mut Peekable<Iter<'_, Token>>,
        is_peek: bool,
    ) -> CompilerResult<String> {
        // int | char | boolean
        let token = if is_peek {
            tokens.peek().copied()
        } else {
            tokens.next()
        }
        .ok_or_else(|| CompilerError::Program("no token".into()))?;
        if let Token::Keyword(keyword) = token
            && [KeywordType::Int, KeywordType::Char, KeywordType::Boolean].contains(keyword)
        {
            return Ok(keyword.to_xml());
        }
        // className
        super::expression::ExpressionCompilerUnit::flow_unit_identifier(token)
    }
}
